function draw() {

  canvas=document.getElementById('tetraCanvas');

  if (canvas.getContext) {

    // Create 2d context in canvas
    tetraCan=canvas.getContext('2d');

    // Canvas Vars:
    var GRIDSIZE_X = 9; // 10 wide
    var GRIDSIZE_Y = 19; // 20 tall
    GRID_OFFSET = 5;
    GRID_LINEWIDTH = 2;
    BLOCK_SIZE = 25 - GRID_LINEWIDTH;

    // Game State/Stat. Vars:
    gameInProgress = true;
    enableInput = true;
    paused = false;
    var linesCleared = 0;
    var totalLinesCleared = 0;
    var score = 0;
    var singleLineScore = 100;
    var doubleLineScore = 200;
    var tripleLineScore = 600;
    var tetrisLineScore = 2400;
    var level = 1;
    var levelMultiplyer = 1.5;
    var levelSpeedModifier = 0.75;
    var newCacheBlock;
    var dropDelay = 2000; // 1/2fps... 17 would be ~60fps.
    var lineDropBonus = 10; // bonus you get per line for a hard drop

    // Misc Vars:
    var color = new Array(0);
    color['blue'] = 230; color['purple'] = 275; color['yellow'] = 60;
    color['green'] = 110; color['lightBlue'] = 180; color['red'] = 0;
    color['orange'] = 30;
    var hueShift = 0;
    var highlight = '85%';
    var lowlight = '15%';

    // Arrays for the current block type, face, & position...
    var currentBlock = new Array(3);
    currentBlock['type']=null; // type (L, Z, S, etc.)
    currentBlock['face']=null; // facing direction
    currentBlock['pose']=new Array(2); // position, [x,y], from the top-left corner
    currentBlock['pose']['x']=0;
    currentBlock['pose']['y']=0;

    function compBlockXY2(phyLoc) { // computes X2/Y2 for a cell
      var xy2 = BLOCK_SIZE + phyLoc;
      return xy2;
    }

    // Arrays for each cell's occupancy state and color... gridCells[i][i2] = gridCells[x][y]
    gridCells = new Array(GRIDSIZE_Y);
    for (var i=0; i<=GRIDSIZE_Y; i++) { // ...where i is y...
      gridCells[i] = new Array(GRIDSIZE_X);
      for (var i2=0; i2<=GRIDSIZE_X; i2++) { // ...and i2 is x...
        gridCells[i][i2] = new Array(2);
        gridCells[i][i2]['occupied'] = false;
        gridCells[i][i2]['color'] = null;
      }
    }

    // Arrays for each cell's physical (pixel) position
    gridCellsPhy = new Array(GRIDSIZE_Y);
    for (var i=0; i<=GRIDSIZE_Y; i++) {
      gridCellsPhy[i] = new Array(GRIDSIZE_X);
      for (var i2=0; i2<=GRIDSIZE_X; i2++) {
        gridCellsPhy[i][i2] = new Array(2);
        gridCellsPhy[i][i2]['x'] = compBlockLoc(i2);
        gridCellsPhy[i][i2]['y'] = compBlockLoc(i);
      }
    }

    // Primary Functions:

  function reSetInterval() {
    moveIntervalId=setInterval(function() {

      if (paused!==true) {
        canvas.moveBlock('down');
      }
      if (totalLinesCleared >= 15 * level) {
        levelUp();
      }
      drawBase();
    }, dropDelay);
  }  

  canvas.gameOver = function(how) { // Ends game
    if (how=='flood') {
      clearInterval(moveIntervalId);
      gameInProgress=false;
      alert('Game over! The blocks reached the top of the board');
    }
  }

  canvas.newGame = function() { // Start a new game
    for (var i=0; i<=GRIDSIZE_Y; i++) {
      for (var i2=0; i2<=GRIDSIZE_X; i2++) { // clear the board
        gridCells[i][i2]['occupied'] = false;
        gridCells[i][i2]['color'] = null;
      }
    }
    gameInProgress=true;
    reSetInterval();

    level=1;
    totalLinesCleared=0;
    score=0;
    dropDelay=2000;
    hueShift=0;
    paused=false;
    newBlock();
    canvas.moveBlock('down');
  }

  canvas.pauseGame = function() { // Pause the game
    if (gameInProgress===true) {
      if (paused===false) {
        paused=true;
        clearInterval(moveIntervalId);
        tetraCan.fillStyle = "#000";
        tetraCan.fillRect(GRID_OFFSET,GRID_OFFSET,gridCellsPhy[19][9]['x']+BLOCK_SIZE-GRID_LINEWIDTH,gridCellsPhy[19][9]['y']+BLOCK_SIZE-GRID_LINEWIDTH);
        tetraCan.fillStyle = "#CCC";
        tetraCan.font = "700 32px 'Dejavu Sans Mono', monospace";
        tetraCan.fillText("[Paused]", gridCellsPhy[9][2]['x'], gridCellsPhy[9][2]['y']);
      } else {
        drawBase();
        reSetInterval();
        paused=false;
      }
    }
  }

  function levelUp() {
    if (hueShift+60 >= 360) {
      hueShift -= 330;
    } else {
      hueShift += 60;
    }

    dropDelay *= levelSpeedModifier;
    level++;
    clearInterval(moveIntervalId);
    reSetInterval();
  }
  function checkRows() { // Check for cleared rows
    var tmpArray = new Array(GRIDSIZE_X);
        for (var z=0; z<=GRIDSIZE_X; z++) {
          tmpArray[z] = new Array(2);
          tmpArray[z]['occupied'] = false;
          tmpArray[z]['color'] = null;
        }
    var cellsInRow = 0;
    var rows = new Array(0);
    for (var i=GRIDSIZE_Y; i>=0; i--) { // Iterate from bottom to top of grid
      for (var i2=0; i2<=GRIDSIZE_X; i2++) { // iterate from left to right
        if (gridCells[i][i2]['occupied']===true) {
          cellsInRow++;
        }
      }
      if (cellsInRow > GRIDSIZE_X) {
        rows.push(i)
      }
      cellsInRow = 0;
    }

    var fullRows = rows.length

    for (var i=fullRows; i>=0; i--) {
      gridCells.splice(rows[i],1); //Remove (clear) Row
      gridCells.reverse(); //Flip Grid
      gridCells[GRIDSIZE_Y] = new Array(GRIDSIZE_X); //Add an empty row
      for (var i2=0; i2<=GRIDSIZE_X; i2++) {
        gridCells[GRIDSIZE_Y][i2] = new Array(2);
        gridCells[GRIDSIZE_Y][i2]['occupied'] = false;
        gridCells[GRIDSIZE_Y][i2]['color'] = null;
      }
      gridCells.reverse(); //Unflip Grid

    }
    return fullRows;  // Returns the number of rows cleared
  }

  // This function rotates the active block in the given direction
  canvas.rotateBlock=function(direction) {
    var x=currentBlock['pose']['x'];
    var y=currentBlock['pose']['y'];
    if (0 <= currentBlock['type'] <= 6) {
      if (currentBlock['type'] <= 2) {
        if (0 <= currentBlock['face'] <= 3) {
          if (direction == 'left') {
            if (currentBlock['face'] > 0) {
              if (opBlock(currentBlock['type'],currentBlock['face']-1,x,y,'check')===false) {
                currentBlock['face']--;
              }
            } else if (opBlock(currentBlock['type'],3,x,y,'check')===false) { // 'face' = 0
              currentBlock['face']=3;
            }
          } else if (currentBlock['face'] < 3) { // direction = 'right'
            if (opBlock(currentBlock['type'],currentBlock['face']+1,x,y,'check')===false) {
              currentBlock['face']++;
            }
          } else if (opBlock(currentBlock['type'],0,x,y,'check')===false) { // 'face' = 3
            currentBlock['face']=0;
          }
        } else { // this shouldn't happen
          console.log('Invalid block face \''+ currentBlock['face'] +'\' for block type \''+ currentBlock['type'] +'\'.');
          currentBlock['face']=0; // set 'face' to a safe value
        }
      } else if (currentBlock['type'] <= 5) { // 'type' > 2
        if (currentBlock['face']==0) {
          if (opBlock(currentBlock['type'],1,x,y,'check')===false) {
            currentBlock['face']=1;
          }
        } else if (opBlock(currentBlock['type'],currentBlock['face']-1,x,y,'check')===false) { // 'face' = 1
          currentBlock['face']=0;
        }
      } else { // 'type' = 6
        currentBlock['face']=0;
      }
    } else { // this shouldn't happen either
      console.log('Invalid block type, \''+ currentBlock['type'] +'\'.');
    }
    drawBase();
  }

  // This function moves the active block in the given direction.
  canvas.moveBlock=function(direction) {
    enableInput=false; //disable input while moving block
    var x=currentBlock['pose']['x'];
    var y=currentBlock['pose']['y'];

    switch (direction) {
      case 'left':
        if (opBlock(currentBlock['type'],currentBlock['face'],x-1,y,'check')===false) {
          currentBlock['pose']['x'] -= 1;
        }
        break;
      case 'right':
        if (opBlock(currentBlock['type'],currentBlock['face'],x+1,y,'check')===false) {
          currentBlock['pose']['x'] += 1;
        }
        break;
      case 'down':
        if (opBlock(currentBlock['type'],currentBlock['face'],x,y+1,'check')===false) {
          currentBlock['pose']['y'] += 1;
        } else {
          opBlock(currentBlock['type'],currentBlock['face'],x,y,'occupy');
          linesCleared = checkRows();
          totalLinesCleared += linesCleared;
          if (linesCleared > 0) {
            switch (linesCleared) {
              case 1: score += linesCleared * (levelMultiplyer * level * singleLineScore); break;
              case 2: score += linesCleared * (levelMultiplyer * level * doubleLineScore); break;
              case 3: score += linesCleared * (levelMultiplyer * level * tripleLineScore); break;
              case 4: score += linesCleared * (levelMultiplyer * level * tetrisLineScore); break;
            }
            linesCleared = 0;
          }
          newBlock();
        }
        break;
      case 'bottom':
        while (opBlock(currentBlock['type'],currentBlock['face'],x,y+1,'check')===false) {
          y +=1;
          currentBlock['pose']['y'] += 1;
          score += lineDropBonus * (levelMultiplyer * level);
        }
        opBlock(currentBlock['type'],currentBlock['face'],x,y,'occupy');
        linesCleared = checkRows();
        totalLinesCleared += linesCleared;
        if (linesCleared > 0) {
          switch (linesCleared) {
            case 1: score += linesCleared * (levelMultiplyer * level * singleLineScore); break;
            case 2: score += linesCleared * (levelMultiplyer * level * doubleLineScore); break;
            case 3: score += linesCleared * (levelMultiplyer * level * tripleLineScore); break;
            case 4: score += linesCleared * (levelMultiplyer * level * tetrisLineScore); break;
          }
          linesCleared = 0;
        }
        newBlock();
        break;
    }
    drawBase();
  enableInput=true; //re-enable input
  }

  // This function sets the canvas fillStyle to
  //  a hue as specified in the array "color".
  canvas.setColor = function(newColor,lightness) {
    var hue = color[newColor]+hueShift;
    switch (lightness) {
      case 0: tetraCan.fillStyle = 'hsl('+ hue +',80%,50%)'; break; // normal
      case 1: tetraCan.strokeStyle = 'hsl('+ hue +',80%,'+ highlight +')'; break; // highlight
      case 2: tetraCan.strokeStyle = 'hsl('+ hue +',80%,'+ lowlight +')'; break; // lowlight
    }
  }
    // Function to draw the highlight and lowlight around a block
    canvas.drawHighLowlight = function(lcolor, bx, by) {
      bx = compBlockLoc(bx);
      by = compBlockLoc(by);

      canvas.setColor(lcolor,1);
      tetraCan.lineWidth= (1/4) * GRID_LINEWIDTH;
      tetraCan.beginPath();
      tetraCan.moveTo(bx, by+BLOCK_SIZE);
      tetraCan.lineTo(bx, by);
      tetraCan.lineTo(bx+BLOCK_SIZE, by);
      tetraCan.lineTo(bx+BLOCK_SIZE-GRID_LINEWIDTH, by+GRID_LINEWIDTH);
      tetraCan.lineTo(bx+GRID_LINEWIDTH, by+GRID_LINEWIDTH);
      tetraCan.lineTo(bx+GRID_LINEWIDTH, by+BLOCK_SIZE-GRID_LINEWIDTH);
      tetraCan.closePath();
      tetraCan.stroke();

      canvas.setColor(lcolor,2);
      tetraCan.beginPath();
      tetraCan.moveTo(bx, by+BLOCK_SIZE);
      tetraCan.lineTo(bx+BLOCK_SIZE, by+BLOCK_SIZE);
      tetraCan.lineTo(bx+BLOCK_SIZE, by);
      tetraCan.lineTo(bx+BLOCK_SIZE-GRID_LINEWIDTH, by+GRID_LINEWIDTH);
      tetraCan.lineTo(bx+BLOCK_SIZE-GRID_LINEWIDTH, by+BLOCK_SIZE-GRID_LINEWIDTH);
      tetraCan.lineTo(bx+GRID_LINEWIDTH, by+BLOCK_SIZE-GRID_LINEWIDTH);
      tetraCan.closePath();
      tetraCan.stroke();
    }
  // This function provides a simple way of choosing
  //  the shape, position, and orientation of a block;
  //  most useful for random selection.
   function opBlock(block,faces,x,y,action) {
     if (!action || action=='draw') {
       switch(block) {
        case 0: Lshape (faces,x,y); break;
        case 1: L2shape(faces,x,y); break;
        case 2: Tshape (faces,x,y); break;
        case 3: lshape (faces,x,y); break;
        case 4: Zshape (faces,x,y); break;
        case 5: Sshape (faces,x,y); break;
        case 6: Oshape (faces,x,y); break;
       }
     } else {
       switch(block) {
        case 0: return Lshape (faces,x,y,action); break;
        case 1: return L2shape(faces,x,y,action); break;
        case 2: return Tshape (faces,x,y,action); break;
        case 3: return lshape (faces,x,y,action); break;
        case 4: return Zshape (faces,x,y,action); break;
        case 5: return Sshape (faces,x,y,action); break;
        case 6: return Oshape (faces,x,y,action); break;
       }
     }
   }

   function newBlock() {
     var newFace;
     if (newCacheBlock === null) {
       newCacheBlock = Math.round(Math.random()*6);
     }

     if (newCacheBlock<=2) {
       if (currentBlock['face']<=3) {
         newFace=currentBlock['face'] + 3 - (currentBlock['face']*2)
       } else {
         newFace=2;
       }
     } else if (newCacheBlock<=5) {
       if (currentBlock['face']%2==0) {
         newFace=0;
       } else {
         newFace=1;
       }
     } else {
       newFace=0;
     }

     if (opBlock(newCacheBlock,newFace,4,0,'check')===true) {
       canvas.gameOver('flood');
     } else {
       currentBlock['type']=newCacheBlock; // make currentBlock = cache block,
       currentBlock['face']=newFace;
       currentBlock['pose']['x']=4;
       currentBlock['pose']['y']=0;
       newCacheBlock = Math.round(Math.random()*6);
     }
   }

  function drawBase() {
    // Clear the canvas
    tetraCan.clearRect(0,0,400,600);

    // Draw score, level, and lines cleared:
    tetraCan.fillStyle = "#000";
    tetraCan.font = "700 24px 'Dejavu Sans Mono', monospace";
    tetraCan.fillText("Level: " + level, 270, 160);
    tetraCan.font = "700 26px 'Dejavu Sans Mono', monospace";
    tetraCan.fillText("Lines: " + totalLinesCleared, 20, 540);
    tetraCan.fillText("Score: " + score, 20, 570);

    // Draw Preview Box:
    tetraCan.strokeStyle = "rgba(0,0,160,0.35)";
    tetraCan.fillStyle = "rgb(0,0,0)";
    tetraCan.lineWidth = GRID_LINEWIDTH;
    tetraCan.fillRect(266, 6, 126, 126);
    tetraCan.strokeRect(266, 6, 126, 126);
    // Setup lines for grid
    tetraCan.lineWidth = GRID_LINEWIDTH;

    // Draw Grid
    tetraCan.fillRect(GRID_OFFSET+1, 5, 250, 502);
    for (var i = GRID_OFFSET + 1; i <= 256; i += 25) { // Vertical Lines
      tetraCan.beginPath();
      tetraCan.moveTo(i, 5);
      tetraCan.lineTo(i, 506);
      tetraCan.stroke();
    }
    for (var i = GRID_OFFSET + 1; i <= 506; i += 25) { // Horizontal Lines
      tetraCan.beginPath();
      tetraCan.moveTo(6, i);
      tetraCan.lineTo(256, i);
      tetraCan.stroke();
    }

    // Draw all the occupied cells
    for (var i=0; i<=GRIDSIZE_X; i++) {
      for (var i2=0; i2<=GRIDSIZE_Y; i2++) {
        if (gridCells[i2][i]['occupied']===true) {
          canvas.setColor(gridCells[i2][i]['color'],0);
          tetraCan.fillRect(gridCellsPhy[i2][i]['x'], gridCellsPhy[i2][i]['y'], BLOCK_SIZE, BLOCK_SIZE)
          canvas.drawHighLowlight(gridCells[i2][i]['color'], i, i2);
        } else {
          tetraCan.fillStyle = "rgb(0,0,0)";
          tetraCan.fillRect(gridCellsPhy[i2][i]['x'], gridCellsPhy[i2][i]['y'], BLOCK_SIZE, BLOCK_SIZE)
        }
      }
    }

    // Draw the active block
    opBlock(currentBlock['type'],currentBlock['face'],currentBlock['pose']['x'],currentBlock['pose']['y']);

    // Draw the preview block (next in queue)
    opBlock(newCacheBlock,0,12.5,1);
  }

  drawBase();

  canvas.newGame();

  }
}
